package cn.cas.mango.dbscan;

import cn.cas.mango.dto.param.HotKeywords;
import cn.cas.mango.util.Pair;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 聚合簇，簇中有许多data
 */
public class Cluster implements Comparable<Cluster> {
    // 簇中的第一个元素
    private final Data core;
    // 簇中的元素，包括第一个元素
    private final List<Data> sim;
    private final List<String> neighborsId;
    // 核心点
    private final List<Data> cores;
    private Set<String> keywords;

    private Map<String, Integer> keywordsCast;

    private Pair<Integer, Double> layerThresh;



    // 拓展邻居形成的簇
    Map<Pair<String, String>, Double> expandedEdge;

    public Cluster(Data core, Pair<Integer, Double> layerThresh) {
        expandedEdge = new LinkedHashMap<>();
        sim = new ArrayList<>();
        keywordsCast = new LinkedHashMap<>();
        this.core = core;
        add2Sim(core);
        neighborsId = new ArrayList<>();
        cores = new ArrayList<>();
        keywords = new LinkedHashSet<>();
        addCores(core);
        this.layerThresh = layerThresh;
    }

  public Map<Pair<String, String>, Double> getExpandedEdge() {
    return expandedEdge;
  }

  public void addExpandedEdge(Data data1, Data data2) {
      expandedEdge.put(new Pair<>(data1.getId(), data2.getId()), data1.calSim(data2));
    }

    public Data getCore() {
      return core;
    }

    public Set<String> getKeyWords() {
        return keywords;
    }
    /**
     * 往簇中添加一个元素, 特别是加入neighbor
     * @param data，要添加的元素
     */
    public void add(Data data) {
        add2Sim(data);
        neighborsId.add(data.getId());
    }

    private void add2Sim(Data data) {
      for (String keyword: data.getKeywords().getKeywords()) {
        keywordsCast.compute(keyword, (k, v)-> v == null ? 1: v + 1);
      }
      sim.add(data);
      data.setAdded();
    }

    private List<String> recurFind(int layer, String key, Map<Integer, Map<String, List<String>>> map2Original) {
      if (layer == 0) {
        return map2Original.get(layer).get(key);
      }
      List<String> keys = map2Original.get(layer).get(key);
      List<String> ret = new ArrayList<>();
      for (String temp: keys) {
        ret.addAll(recurFind(layer - 1, temp, map2Original));
      }
      return ret;
    }

    public Map<String, Integer> getTopKeywords(Map<Integer, Map<String, List<String>>> map2Original) {
      Map<String, Double> keywordsRatio;
      if (map2Original.containsKey(-1)) {
        Integer layer = layerThresh.getKey();
        Map<String, List<String>> nowLayerTotal = map2Original.get(layer);
        List<String> strings = nowLayerTotal.get(core.getId());
        List<String> originalKey = new ArrayList<>();
        for (String string: strings) {
          originalKey.addAll(recurFind(layer-1, string, map2Original));
        }
        Map<String, Integer> tempKeywordsCast = new LinkedHashMap<>();
        for (String key: originalKey) {
          Map<String, List<String>> orginalMap = map2Original.get(-1);
          List<String> keywords = orginalMap.get(key);
          keywords.forEach(keyword->tempKeywordsCast.compute(keyword, (k, v)-> v == null ? 1: v + 1));
        }
        keywordsRatio = new LinkedHashMap<String, Double>(){{
          for (String key: tempKeywordsCast.keySet()) {
            put(key, tempKeywordsCast.get(key) * 1.0 / originalKey.size());
          }
        }};
      } else {
        keywordsRatio = new LinkedHashMap<String, Double>(){{
          for (String key: keywordsCast.keySet()) {
            put(key, keywordsCast.get(key) * 1.0 / sim.size());
          }
        }};
      }

      List<Map.Entry<String, Double>> list = new ArrayList<>(keywordsRatio.entrySet());
      list.sort((x ,y)->-x.getValue().compareTo(y.getValue()));
      List<String> collect = getSub(list);
      Map<String, Integer> topKeywords = new LinkedHashMap<>();
      for (String key: collect) {
        topKeywords.put(key, keywordsCast.get(key));
      }
      return topKeywords;
    }

//    private List<String> getSub(List<Map.Entry<String, Double>> list) {
//      List<String> collect = list.subList(0, Math.min(list.size(), 30)).stream().filter(x -> x.getValue() > 0.5).map(Map.Entry::getKey).collect(Collectors.toList());
//      if (collect.isEmpty()) {
//        if (list.isEmpty()) return collect;
//        else  return list.subList(0, Math.min(list.size(), 2)).stream().map(x->x.getKey()).collect(Collectors.toList());
//      } else return collect;
//    }

  private List<String> getSub(List<Map.Entry<String, Double>> list) {
      List<String> collect = list.subList(0, Math.min(list.size(), layerThresh.getKey())).stream().filter(x -> x.getValue() > layerThresh.getValue()).map(Map.Entry::getKey).collect(Collectors.toList());
      if (collect.isEmpty()) {
        if (list.isEmpty()) return collect;
        else  return list.subList(0, Math.min(list.size(), 2)).stream().map(x->x.getKey()).collect(Collectors.toList());
      } else return collect;
    }
    private Map<String, Integer> getAllEnt(List<Set<String>> list1)  {
      List<String> list = new ArrayList<>();
      for (Set<String>set: list1) {
        list.addAll(set);
      }
      Map<String, Integer> map = new HashMap<>();
      for (String string: list) {
        if (!map.containsKey(string)) {
          map.put(string, 0);
        }
        map.put(string, map.get(string) + 1);
      }
      return map;
    }

    private Set<String> calAllEnt(Map<String, Integer> map) {
      Map<String, Double> mapDouble = new HashMap<>();
      int size = sim.size();
      for (String string: map.keySet()) {
        mapDouble.put(string, map.get(string) * 1.0 / size);
      }
      List<Map.Entry<String, Double>> list = new ArrayList<>(mapDouble.entrySet());
      return new HashSet<>(getSub(list));

    }

    public void getTopEnt(
                          Set<String> entitiesPerson,
                          Set<String> entitiesPlace,
                          Set<String> entitiesOrganization,
                          Set<String> entities) {
      Map<String, Integer> inPerson = getAllEnt(sim.stream().map(x->x.getEntitiesPerson()).collect(Collectors.toList()));
      Map<String, Integer> inPlace = getAllEnt(sim.stream().map(x->x.getEntitiesPlace()).collect(Collectors.toList()));
      Map<String, Integer> inOrg = getAllEnt(sim.stream().map(x->x.getEntitiesOrganization()).collect(Collectors.toList()));


      entitiesPerson.addAll(calAllEnt(inPerson));
      entitiesPlace.addAll(calAllEnt(inPlace));
      entitiesOrganization.addAll(calAllEnt(inOrg));
      entities.addAll(Data.calUnion(Data.calUnion(entitiesPerson, entitiesPlace), entitiesOrganization));
    }

    public void addCores(Data data) {
        int coresSize = cores.size();
        if (coresSize == 0) {
            keywords.addAll(data.getKeywords().getKeywords());
        } else {
            Data lastData = cores.get(coresSize - 1);
            Set<String> interKeywords = Data.calInter(lastData.getKeywords().getKeywords(), data.getKeywords().getKeywords());
            if (coresSize == 1) {
                keywords = interKeywords;
            } else {
                keywords = Data.calUnion(keywords, interKeywords);
            }
        }
        cores.add(data);

    }

  public List<String> getNeighborsId() {
    return neighborsId;
  }

  /**
     * 按照第一个元素去排序
     * @param c2 the object to be compared.
     */
    @Override
    public int compareTo(Cluster c2) {
        return core.compareTo(c2.core);
    }

    /**
     * 获取簇中所有元素
     */
    public List<Data> getSim() {
        return sim;
    }

    public int size() {
      return sim.size();
    }
}
